/*
 * Copyright (c) 2001 William L. Pitts
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are freely
 * permitted provided that the above copyright notice and this
 * paragraph and the following disclaimer are duplicated in all
 * such forms.
 *
 * This software is provided "AS IS" and without any express or
 * implied warranties, including, without limitation, the implied
 * warranties of merchantability and fitness for a particular
 * purpose.
 */

#include <common.h>
#include <command.h>
#include <linux/ctype.h>
#include <net.h>
#include <elf.h>
#include <asm/mipsregs.h>

#include "loongson_smbios.h"

extern void *build_boot_param(void);

/* Allow ports to override the default behavior */
static void do_bootls_exec(ulong (*entry)(int, void *, void *), int argc, char *const argv[])
{
	char *myargv[2];
	void *bp;
	myargv[0] = "bootargs";
	myargv[1] = env_get("bootargs");

	write_smbios_table(SMBIOS_PHYSICAL_ADDRESS);
	bp = build_boot_param();

//	write_c0_compare(read_c0_count());

	entry(2, myargv, bp);
}

/* ======================================================================
 * Determine if a valid ELF image exists at the given memory location.
 * First looks at the ELF header magic field, the makes sure that it is
 * executable and makes sure that it is for a PowerPC.
 * ====================================================================== */
static int valid_elf_image_ls(unsigned long addr)
{
	Elf32_Ehdr *ehdr;		/* Elf header structure pointer */

	ehdr = (Elf32_Ehdr *)addr;

	if (!IS_ELF(*ehdr)) {
		printf ("## No elf image at address 0x%08lx\n", addr);
		return 0;
	}

	if (ehdr->e_type != ET_EXEC) {
		printf ("## Not a 32-bit elf image at address 0x%08lx\n", addr);
		return 0;
	}

	if (ehdr->e_ident[EI_CLASS] == ELFCLASS32) {
		return 1;
	} else if(ehdr->e_ident[EI_CLASS] == ELFCLASS64) {
		return 2;
	}

	return 3;
}

/* ======================================================================
 * A very simple elf loader, assumes the image is valid, returns the
 * entry point address.
 * ====================================================================== */
static unsigned long load_elf_image(unsigned long addr)
{
	Elf32_Ehdr *ehdr;		/* Elf header structure pointer     */
	Elf32_Shdr *shdr;		/* Section header structure pointer */
	unsigned char *strtab = 0;	/* String table pointer             */
	unsigned char *image;		/* Binary image pointer             */
	int i;				/* Loop counter                     */

	ehdr = (Elf32_Ehdr *)addr;

	/* Find the section header string table for output info */
	shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff +
			     (ehdr->e_shstrndx * sizeof(Elf32_Shdr)));

	if (shdr->sh_type == SHT_STRTAB)
		strtab = (unsigned char *)(addr + shdr->sh_offset);

	/* Load each appropriate section */
	for (i = 0; i < ehdr->e_shnum; ++i) {
		shdr = (Elf32_Shdr *)(addr + ehdr->e_shoff +
				     (i * sizeof(Elf32_Shdr)));

		if (!(shdr->sh_flags & SHF_ALLOC) ||
		    shdr->sh_addr == 0 || shdr->sh_size == 0) {
			continue;
		}

		if (strtab) {
			debug("%sing %s @ 0x%08lx (%ld bytes)\n",
			      (shdr->sh_type == SHT_NOBITS) ? "Clear" : "Load",
			       &strtab[shdr->sh_name],
			       (unsigned long)shdr->sh_addr,
			       (long)shdr->sh_size);
		}

		if (shdr->sh_type == SHT_NOBITS) {
			memset((void *)(uintptr_t)shdr->sh_addr, 0,
			       shdr->sh_size);
		} else {
			image = (unsigned char *)addr + shdr->sh_offset;
			memcpy((void *)(uintptr_t)shdr->sh_addr,
			       (const void *)image, shdr->sh_size);
		}
		flush_cache(shdr->sh_addr, shdr->sh_size);
		flush_cache(rounddown(shdr->sh_addr, ARCH_DMA_MINALIGN),
			    roundup((shdr->sh_addr + shdr->sh_size),
				    ARCH_DMA_MINALIGN) -
			    rounddown(shdr->sh_addr, ARCH_DMA_MINALIGN));
	}

	return ehdr->e_entry;
}

/* ======================================================================
 * A note simple elf loader from pmon, assumes the image is valid, returns the
 * entry point address.
 * ====================================================================== */
static unsigned long load_elf64_image(unsigned long addr)
{
	Elf64_Ehdr *ehdr;		/* Elf header structure pointer     */
	Elf64_Shdr *shdr;		/* Section header structure pointer */
	unsigned char *strtab = 0;	/* String table pointer             */
	unsigned char *image;		/* Binary image pointer             */
	int i;				/* Loop counter                     */

	ehdr = (Elf64_Ehdr *)addr;

	/* Find the section header string table for output info */
	shdr = (Elf64_Shdr *)(addr + (ulong)ehdr->e_shoff +
			     (ehdr->e_shstrndx * sizeof(Elf64_Shdr)));

	if (shdr->sh_type == SHT_STRTAB)
		strtab = (unsigned char *)(addr + (ulong)shdr->sh_offset);

	/* Load each appropriate section */
	for (i = 0; i < ehdr->e_shnum; ++i) {
		shdr = (Elf64_Shdr *)(addr + (ulong)ehdr->e_shoff +
				     (i * sizeof(Elf64_Shdr)));

		if (!(shdr->sh_flags & SHF_ALLOC) ||
		    shdr->sh_addr == 0 || shdr->sh_size == 0) {
			continue;
		}

		if (strtab) {
			debug("%sing %s @ 0x%08lx (%ld bytes)\n",
			      (shdr->sh_type == SHT_NOBITS) ? "Clear" : "Load",
			       &strtab[shdr->sh_name],
			       (unsigned long)shdr->sh_addr,
			       (long)shdr->sh_size);
		}

		if (shdr->sh_type == SHT_NOBITS) {
			memset((void *)(uintptr_t)shdr->sh_addr, 0,
			       shdr->sh_size);
		} else {
			image = (unsigned char *)addr + (ulong)shdr->sh_offset;
			memcpy((void *)(uintptr_t)shdr->sh_addr,
			       (const void *)image, shdr->sh_size);
		}
		flush_cache (shdr->sh_addr, shdr->sh_size);
	}

	return ehdr->e_entry;
}

/* ======================================================================
 * Interpreter command to boot an arbitrary ELF image from memory.
 * ====================================================================== */
int do_bootls(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])
{
	unsigned long addr; /* Address of the ELF image     */
	unsigned long rc;   /* Return value from user code  */

	if (argc < 2)
		addr = load_addr;
	else
		addr = simple_strtoul(argv[1], NULL, 16);

	rc = valid_elf_image_ls(addr);
	if (rc == ELFCLASS32) {
		addr = load_elf_image(addr);
	} else if (rc == ELFCLASS64) {
		addr = load_elf64_image(addr);
	} else if (rc == 0) {
		return 0;  // no valid elf image found
	}

	/*
	 * pass address parameter as argv[0] (aka command name),
	 * and all remaining args
	 */
	do_bootls_exec((void *)addr, argc - 1, argv + 1);
	/*
	 * not return
	 */
	return 0;
}

U_BOOT_CMD(
	bootls, CONFIG_SYS_MAXARGS, 0, do_bootls,
	"Boot from an linux vmlinux(ELF image) in memory for Loongson",
	"[-p|-s] [address]\n"
	"\t- load ELF image at [address] via program headers (-p)\n"
	"\t  or via section headers (-s)"
);
